{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# LangGraph Looping Logic\n",
    "\n",
    "We'll showcase two distinct methods for implementing loops in LangGraph:\n",
    "\n",
    "1. **Direct Conditional Routing**: Utilizing conditional edges straight from a processing node.\n",
    "2. **Separate Decision Node**: Employing a dedicated node to handle routing decisions.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup and Imports\n"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "jupyter": {
     "is_executing": true
    }
   },
   "source": [
    "from langgraph.graph import StateGraph, END\n",
    "import random\n",
    "from typing import TypedDict, List\n",
    "from IPython.display import Image, display\n",
    "from rich import print"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Approach 1: Direct Conditional Routing\n",
    "\n",
    "In this approach, we'll create a graph where the conditional edges are attached directly to the processing node. This creates a more compact graph structure.\n",
    "\n",
    "### State Definition\n",
    "\n",
    "We'll create a simple state that tracks a list of numbers and their running total. Our loop will continue adding random numbers until the total reaches a threshold.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define the shape of our state\n",
    "class SumState(TypedDict):\n",
    "    numbers: List[int]\n",
    "    total: int"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### Node Functions\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Entry node: initialize the list and total\n",
    "def init_node(state: SumState) -> SumState:\n",
    "    state[\"numbers\"] = []\n",
    "    state[\"total\"] = 0\n",
    "    return state\n",
    "\n",
    "\n",
    "# Loop node: pick a random number, append it, and update the total\n",
    "def add_number(state: SumState) -> SumState:\n",
    "    num = random.randint(5, 15)\n",
    "    state[\"numbers\"].append(num)\n",
    "    state[\"total\"] += num\n",
    "    return state\n",
    "\n",
    "\n",
    "# Conditional function: decide whether to loop or exit\n",
    "def check_continue(state: SumState) -> str:\n",
    "    if state[\"total\"] < 50:\n",
    "        print(f\"Total is {state['total']}, adding another number...\")\n",
    "        return \"add\"  # go back to the add_number node\n",
    "    else:\n",
    "        print(f\"Reached total {state['total']}. Exiting loop.\")\n",
    "        return \"end\"  # jump to END"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Building the Graph\n",
    "\n",
    "Now, we'll build the graph using direct conditional routing. Observe how the `add` node features conditional edges that can loop back to itself, forming a cycle.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Build the graph\n",
    "graph = StateGraph(SumState)\n",
    "\n",
    "graph.add_node(\"init\", init_node)\n",
    "graph.add_node(\"add\", add_number)\n",
    "\n",
    "# From init → add\n",
    "graph.add_edge(\"init\", \"add\")\n",
    "\n",
    "# From add → either add (loop) or END (using conditional routing)\n",
    "graph.add_conditional_edges(\n",
    "    \"add\",  # source node name\n",
    "    check_continue,  # the decision function\n",
    "    {\n",
    "        \"add\": \"add\",  # loop back to the same node\n",
    "        \"end\": END,  # or terminate the graph\n",
    "    },\n",
    ")\n",
    "\n",
    "# Specify the starting node\n",
    "graph.set_entry_point(\"init\")\n",
    "\n",
    "# Compile and run\n",
    "app = graph.compile()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Visualizing the Graph\n",
    "\n",
    "Let's visualize the graph structure to understand the flow better.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Visualize the Graph (requires graphviz and pygraphviz or pydot)\n",
    "display(Image(app.get_graph().draw_mermaid_png()))\n",
    "# save the graph to a file\n",
    "with open(\"./output//06.1-Looping_Logic.png\", \"wb\") as f:\n",
    "    f.write(app.get_graph().draw_mermaid_png())\n",
    "# Mermaid is a popular diagramming syntax that can be rendered in many tools\n",
    "with open(\"./output/06.1-Looping_Logic.mmd\", \"w\") as f:\n",
    "    f.write(app.get_graph().draw_mermaid())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Running the Graph\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Total is <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">6</span>, adding another number<span style=\"color: #808000; text-decoration-color: #808000\">...</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Total is \u001B[1;36m6\u001B[0m, adding another number\u001B[33m...\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Total is <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">13</span>, adding another number<span style=\"color: #808000; text-decoration-color: #808000\">...</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Total is \u001B[1;36m13\u001B[0m, adding another number\u001B[33m...\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Total is <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">28</span>, adding another number<span style=\"color: #808000; text-decoration-color: #808000\">...</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Total is \u001B[1;36m28\u001B[0m, adding another number\u001B[33m...\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Total is <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">40</span>, adding another number<span style=\"color: #808000; text-decoration-color: #808000\">...</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Total is \u001B[1;36m40\u001B[0m, adding another number\u001B[33m...\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Total is <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">48</span>, adding another number<span style=\"color: #808000; text-decoration-color: #808000\">...</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Total is \u001B[1;36m48\u001B[0m, adding another number\u001B[33m...\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Reached total <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">58</span>. Exiting loop.\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Reached total \u001B[1;36m58\u001B[0m. Exiting loop.\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">\n",
       "Final state:\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\n",
       "Final state:\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'numbers'</span>: <span style=\"font-weight: bold\">[</span><span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">6</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">7</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">15</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">12</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">8</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">10</span><span style=\"font-weight: bold\">]</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'total'</span>: <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">58</span><span style=\"font-weight: bold\">}</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001B[1m{\u001B[0m\u001B[32m'numbers'\u001B[0m: \u001B[1m[\u001B[0m\u001B[1;36m6\u001B[0m, \u001B[1;36m7\u001B[0m, \u001B[1;36m15\u001B[0m, \u001B[1;36m12\u001B[0m, \u001B[1;36m8\u001B[0m, \u001B[1;36m10\u001B[0m\u001B[1m]\u001B[0m, \u001B[32m'total'\u001B[0m: \u001B[1;36m58\u001B[0m\u001B[1m}\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "final_state = app.invoke({\"numbers\": [], \"total\": 0})\n",
    "\n",
    "print(\"\\nFinal state:\")\n",
    "print(final_state)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Approach 2: Separate Decision Node\n",
    "\n",
    "In this approach, we'll use a dedicated decision node that acts as a router. This pattern separates the processing logic from the routing logic, which can be useful for more complex decision-making scenarios.\n",
    "\n",
    "### Node Functions\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Entry node: initialize the list and total (same as before)\n",
    "def init_node(state: SumState) -> SumState:\n",
    "    state[\"numbers\"] = []\n",
    "    state[\"total\"] = 0\n",
    "    return state\n",
    "\n",
    "\n",
    "# Loop node: pick a random number, append it, and update the total (same as before)\n",
    "def add_number(state: SumState) -> SumState:\n",
    "    num = random.randint(5, 15)\n",
    "    state[\"numbers\"].append(num)\n",
    "    state[\"total\"] += num\n",
    "    return state\n",
    "\n",
    "\n",
    "# Dummy pass-through node; real branching happens in the conditional edges\n",
    "def start(state: SumState) -> SumState:\n",
    "    return state\n",
    "\n",
    "\n",
    "# Dummy pass-through node; real branching happens in the conditional edges\n",
    "def decide_node(state: SumState) -> SumState:\n",
    "    return state\n",
    "\n",
    "\n",
    "# Conditional function: decide whether to loop or exit (same as before)\n",
    "def check_continue(state: SumState) -> str:\n",
    "    if state[\"total\"] < 50:\n",
    "        print(f\"Total is {state['total']}, adding another number...\")\n",
    "        return \"add\"  # go back to add_number node\n",
    "    else:\n",
    "        print(f\"Reached total {state['total']}. Exiting loop.\")\n",
    "        return \"end\"  # jump to END"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Building the Graph with Decision Node\n",
    "\n",
    "This time we'll create a more explicit flow: `init → add → decide → (add or END)`\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Build the graph\n",
    "graph = StateGraph(SumState)\n",
    "\n",
    "graph.add_node(\"init\", init_node)\n",
    "graph.add_node(\"start\", start)\n",
    "graph.add_node(\"add\", add_number)\n",
    "\n",
    "graph.add_node(\"decide\", decide_node)\n",
    "\n",
    "\n",
    "# Wire up the flow: init → add → decide → (add or END)\n",
    "graph.add_edge(\"init\", \"start\")\n",
    "graph.add_edge(\"start\", \"add\")\n",
    "graph.add_edge(\"add\", \"decide\")\n",
    "\n",
    "graph.add_conditional_edges(\n",
    "    \"decide\",  # source node name\n",
    "    check_continue,  # decision function\n",
    "    {\n",
    "        \"add\": \"start\",  # loop back to the add node\n",
    "        \"end\": END,  # or terminate\n",
    "    },\n",
    ")\n",
    "\n",
    "# Specify the starting node\n",
    "graph.set_entry_point(\"init\")\n",
    "\n",
    "# Compile and run\n",
    "app = graph.compile()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Visualizing the Second Graph\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Total is <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">6</span>, adding another number<span style=\"color: #808000; text-decoration-color: #808000\">...</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Total is \u001B[1;36m6\u001B[0m, adding another number\u001B[33m...\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Total is <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">11</span>, adding another number<span style=\"color: #808000; text-decoration-color: #808000\">...</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Total is \u001B[1;36m11\u001B[0m, adding another number\u001B[33m...\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Total is <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">16</span>, adding another number<span style=\"color: #808000; text-decoration-color: #808000\">...</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Total is \u001B[1;36m16\u001B[0m, adding another number\u001B[33m...\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Total is <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">26</span>, adding another number<span style=\"color: #808000; text-decoration-color: #808000\">...</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Total is \u001B[1;36m26\u001B[0m, adding another number\u001B[33m...\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Total is <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">37</span>, adding another number<span style=\"color: #808000; text-decoration-color: #808000\">...</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Total is \u001B[1;36m37\u001B[0m, adding another number\u001B[33m...\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Total is <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">42</span>, adding another number<span style=\"color: #808000; text-decoration-color: #808000\">...</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Total is \u001B[1;36m42\u001B[0m, adding another number\u001B[33m...\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Reached total <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">54</span>. Exiting loop.\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Reached total \u001B[1;36m54\u001B[0m. Exiting loop.\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">\n",
       "Final state:\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\n",
       "Final state:\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'numbers'</span>: <span style=\"font-weight: bold\">[</span><span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">6</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">5</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">5</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">10</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">11</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">5</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">12</span><span style=\"font-weight: bold\">]</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'total'</span>: <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">54</span><span style=\"font-weight: bold\">}</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001B[1m{\u001B[0m\u001B[32m'numbers'\u001B[0m: \u001B[1m[\u001B[0m\u001B[1;36m6\u001B[0m, \u001B[1;36m5\u001B[0m, \u001B[1;36m5\u001B[0m, \u001B[1;36m10\u001B[0m, \u001B[1;36m11\u001B[0m, \u001B[1;36m5\u001B[0m, \u001B[1;36m12\u001B[0m\u001B[1m]\u001B[0m, \u001B[32m'total'\u001B[0m: \u001B[1;36m54\u001B[0m\u001B[1m}\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(Image(app.get_graph().draw_mermaid_png()))\n",
    "\n",
    "with open(\"./output/06.2-Looping_Logic.png\", \"wb\") as f:\n",
    "    f.write(app.get_graph().draw_mermaid_png())\n",
    "\n",
    "# save mermaid graph to a file\n",
    "final_state = app.invoke({\"numbers\": [], \"total\": 0})\n",
    "print(\"\\nFinal state:\")\n",
    "print(final_state)\n",
    "\n",
    "# Save the Mermaid representation of the graph\n",
    "with open(\"./output/06.2-Looping_Logic.mmd\", \"w\") as f:\n",
    "    f.write(app.get_graph().draw_mermaid())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Running the Second Graph\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Total is <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">14</span>, adding another number<span style=\"color: #808000; text-decoration-color: #808000\">...</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "Total is \u001B[1;36m14\u001B[0m, adding another number\u001B[33m...\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">\n",
       "Final state:\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\n",
       "Final state:\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'numbers'</span>: <span style=\"font-weight: bold\">[</span><span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">14</span><span style=\"font-weight: bold\">]</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'total'</span>: <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">14</span><span style=\"font-weight: bold\">}</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001B[1m{\u001B[0m\u001B[32m'numbers'\u001B[0m: \u001B[1m[\u001B[0m\u001B[1;36m14\u001B[0m\u001B[1m]\u001B[0m, \u001B[32m'total'\u001B[0m: \u001B[1;36m14\u001B[0m\u001B[1m}\u001B[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "final_state = app.invoke({\"numbers\": [], \"total\": 0})\n",
    "\n",
    "print(\"\\nFinal state:\")\n",
    "print(final_state)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py312",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
